package cn.blnp.net.flowable.process;

import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.*;
import org.flowable.common.engine.api.repository.EngineResource;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.impl.persistence.entity.DeploymentEntity;
import org.flowable.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.DeploymentBuilder;
import org.flowable.engine.repository.ProcessDefinition;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import java.io.*;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipInputStream;

/**
 * <h3>流程部署案例演示</h3>
 *
 * @author <a href="mailto:blnp.yibin@qq.com">lyb</a>
 * @version 1.0
 * @since 2025/4/30 15:00
 */
@Slf4j
@SpringBootTest
public class FlowDeployTest {

    @Resource
    private RepositoryService repositoryService;
    @Resource
    private RuntimeService runtimeService;
    @Resource
    private TaskService taskService;
    @Resource
    private HistoryService historyService;

    /**
     *  <h2>流程资源说明</h2>
     *  <hr/>
     *  <ul>
     *      <li>
     *          <b>流程定义文件：</b>扩展名为.bpmn20.xml或.bpmn。
     *      </li>
     *      <li>
     *          <b>流程定义图片：</b>用遵循BPMN 2.0规范的各种图形描绘的图片，通常为.png格式。
     *      </li>
     *      <li>
     *          <b>表单文件：</b>用于存储流程挂载的表单内容的文件，扩展名为.form。一个流程可以挂载多个表单。
     *      </li>
     *      <li>
     *          <b>规则文件：</b>使用Drools语法定义的规则，扩展名为.drl。
     *      </li>
     *      <li>
     *          <b>决策表文件：</b>用于存储决策表的文件，扩展名为.dmn
     *      </li>
     *      <li>
     *          <b>案例文件：</b>用于存储案例模型的文件，扩展名为.cmmn.xml。
     *      </li>
     *  </ul>
     **/
    
    /**
     * <p><b>用途:部署接口用途说明<b></p>
     * @author <a href="mailto:blnp.yibin@qq.com">lyb</a>
     * @since 15:59 2025/5/6
     * @params [] 
     * @param  
     * @return void
    **/
    @Test
    public void test() {
        /**
         *  部署接口用途说明
         **/
        //添加输入流资源
        //DeploymentBuilder addInputStream(String resourceName, InputStream inputStream);
        
        //添加classpath下的文件资源
        //DeploymentBuilder addClasspathResource(String resource);
        
        //添加字符串资源
        //DeploymentBuilder addString(String resourceName, String text);
        
        //添加字节数组资源
        //DeploymentBuilder addBytes(String resourceName, byte[] bytes);
        
        //添加ZIP压缩包资源
        //DeploymentBuilder addZipInputStream(ZipInputStream zipInputStream);
        
        //添加BpmnModel模型资源
        //DeploymentBuilder addBpmnModel(String resourceName, BpmnModel bpmnModel);
        
        //禁用XML格式合法性校验
        //DeploymentBuilder disableSchemaValidation();
        
        //禁用BPMN元素合法性校验
        //DeploymentBuilder disableBpmnValidation();
        
        //设置部署名称
        //DeploymentBuilder name(String name);
        
        //设置部署分类
        //DeploymentBuilder category(String category);
        
        //设置部署key
        //DeploymentBuilder key(String key);
        
        //设置父部署ID
        //DeploymentBuilder parentDeploymentId(String parentDeploymentId);
        
        //设置租户ID
        //DeploymentBuilder tenantId(String tenantId);
        
        //设置启用过滤重复部署
        //DeploymentBuilder enableDuplicateFiltering();
        
        //设置流程定义激活日期
        //DeploymentBuilder activateProcessDefinitionsOn(Date date);
        
        //设置部署属性
        //DeploymentBuilder deploymentProperty(String propertyKey, Object propertyValue);
        
        //执行部署
        //Deployment deploy();
    }

    /**
     * <p><b>用途:通过加载输入流的方式进行部署<b></p>
     * @author <a href="mailto:blnp.yibin@qq.com">lyb</a>
     * @since 16:00 2025/5/6
     * @params [] 
     * @param  
     * @return void
    **/
    @Test
    public void deployByInputStream() throws IOException {
        //从文件系统读取资源文件，创建输入流
        try (FileInputStream inputStream = new FileInputStream(
                new File("/Users/bpm/processes/HolidayRequest.bpmn20.xml"))) {
            //创建DeploymentBuilder
            DeploymentBuilder builder = repositoryService.createDeployment();
            //将输入流传递给DeploymentBuilder，同时指定资源名称
            builder.addInputStream("HolidayRequest.bpmn20.xml", inputStream);
            //执行部署
            builder.deploy();
        }
    }

    /**
     * <p><b>用途:通过加载类路径下的资源文件进行部署<b></p>
     * @author <a href="mailto:blnp.yibin@qq.com">lyb</a>
     * @since 16:01 2025/5/6
     * @params [] 
     * @param  
     * @return void
    **/
    @Test
    public void deployByClasspathResource() {
        //创建DeploymentBuilder
        DeploymentBuilder builder = repositoryService.createDeployment();
        //加载classpath下的文件
        builder.addClasspathResource("processes/HolidayRequest.bpmn20.xml");
        //执行部署
        builder.deploy();
    }

    /**
     * <p><b>用途:通过加载字符串的方式进行部署<b></p>
     * @author <a href="mailto:blnp.yibin@qq.com">lyb</a>
     * @since 16:01 2025/5/6
     * @params [] 
     * @param  
     * @return void
    **/
    @Test
    public void deployByString() throws IOException {
        //读取文件并转换为string
        try (FileReader fileReader =
                     new FileReader(new File("/Users/bpm/processes/HolidayRequest.bpmn20.xml"));
             BufferedReader bufferedReader = new BufferedReader(fileReader);) {
            StringBuilder stringBuilder = new StringBuilder();
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                stringBuilder.append(line);
            }
            //创建DeploymentBuilder
            DeploymentBuilder builder = repositoryService.createDeployment();
            //将输入流传递给DeploymentBuilder，同时指定资源名称
            builder.addString("HolidayRequest.bpmn20.xml", stringBuilder.toString());
            //执行部署
            builder.deploy();
        }
    }

    /**
     * <p><b>用途:通过加载字节数组的方式进行部署<b></p>
     * @author <a href="mailto:blnp.yibin@qq.com">lyb</a>
     * @since 16:02 2025/5/6
     * @params [] 
     * @param  
     * @return void
    **/
    @Test
    public void deployByBytes() throws IOException {
        //读取文件并转换为byte[]
        try (FileInputStream inputStream =
                     new FileInputStream(new File("/Users/bpm/processes/HolidayRequest.bpmn20.xml"));
             ByteArrayOutputStream bos = new ByteArrayOutputStream();) {
            byte[] temp = new byte[1024];
            int n;
            while ((n = inputStream.read(temp)) != -1) {
                bos.write(temp, 0, n);
            }
            //创建DeploymentBuilder
            DeploymentBuilder builder = repositoryService.createDeployment();
            //将字节数组传递给DeploymentBuilder，同时指定资源名称
            builder.addBytes("HolidayRequest.bpmn20.xml", bos.toByteArray());
            //执行部署
            builder.deploy();
        }
    }

    /**
     * <p><b>用途:通过加载压缩包的方式进行部署<b></p>
     * @author <a href="mailto:blnp.yibin@qq.com">lyb</a>
     * @since 16:03 2025/5/6
     * @params [] 
     * @param  
     * @return void
    **/
    @Test
    public void deployByZipInputStream() throws IOException {
        //读取ZIP文件
        try (FileInputStream inputStream =
                     new FileInputStream(new File("/Users/bpm/processes/HolidayRequest.zip"));
             ZipInputStream zipInputStream = new ZipInputStream(inputStream);) {
            //创建DeploymentBuilder
            DeploymentBuilder builder = repositoryService.createDeployment();
            //传递ZIP输入流给DeploymentBuilder
            builder.addZipInputStream(zipInputStream);
            //执行部署
            builder.deploy();
        }
    }

    /**
     * <p><b>用途:通过加载BpmnModel的方式进行部署<b></p>
     * @author <a href="mailto:blnp.yibin@qq.com">lyb</a>
     * @since 16:03 2025/5/6
     * @params [] 
     * @param  
     * @return void
    **/
    @Test
    public void deployByBpmnModel() {
        //创建BpmnModel对象
        BpmnModel model = new BpmnModel();
        //创建流程(Process)
        org.flowable.bpmn.model.Process process = new org.flowable.bpmn.model.Process();
        model.addProcess(process);
        process.setId("HolidayRequest");
        process.setName("请假申请流程");
        //创建开始节点
        StartEvent startEvent = new StartEvent();
        startEvent.setId("startEvent1");
        startEvent.setName("开始");
        process.addFlowElement(startEvent);
        //创建任务节点
        UserTask userTask1 = new UserTask();
        userTask1.setId("userTask1");
        userTask1.setName("申请");
        process.addFlowElement(userTask1);
        //创建任务节点
        UserTask userTask2 = new UserTask();
        userTask2.setId("userTask2");
        userTask2.setName("审批");
        process.addFlowElement(userTask2);
        //创建结束节点
        EndEvent endEvent = new EndEvent();
        endEvent.setId("endEvent1");
        endEvent.setName("结束");
        process.addFlowElement(endEvent);
        //创建节点的关联关系
        process.addFlowElement(new SequenceFlow("startEvent1", "userTask1"));
        process.addFlowElement(new SequenceFlow("userTask1", "userTask2"));
        process.addFlowElement(new SequenceFlow("userTask2", "endEvent1"));
        //创建DeploymentBuilder
        DeploymentBuilder builder = repositoryService.createDeployment();
        //传递BpmnModel给DeploymentBuilder
        builder.addBpmnModel("HolidayRequest.bpmn20.xml", model);
        //执行部署
        builder.deploy();
    }

    /**
     *  <h2>部署结果查询：</h2>
     *  <hr/>
     *  <p>
     *   &emsp;&emsp;部署记录存储在ACT_RE_DEPLOYMENT表中，对应Flowable中的实体类DeploymentEntity。DeploymentEntityManager类主要用于操作ACT_RE_DEPLOYMENT表，
     *  如将DeploymentEntity保存到ACT_RE_ DEPLOYMENT表中，或者将数据库记录查询结果转换为DeploymentEntity对象。DeploymentQuery类是Flowable提供的部署记录
     *  查询接口，可以方便地设置查询条件，而DeploymentEntityManager类可以根据设置好查询条件的DeploymentQuery对象执行查询操作，并返回符合条件的DeploymentEntity对象。
     *  </p><br/>
     *
     *  <h3>DeploymentEntity：</h3>
     *  <ul>
     *      <li>
     *          <b>id：</b>部署ID，继承自父类AbstractEntityNoRevision的id属性，对应ACT_RE_DEPLOYMENT表的ID_字段（主键）。
     *      </li>
     *      <li>
     *          <b>name：</b>部署名称，对应ACT_RE_DEPLOYMENT表的NAME_字段。
     *      </li>
     *      <li>
     *          <b>category：</b>流程部署分类，对应ACT_RE_DEPLOYMENT表的CATEGORY_字段。
     *      </li>
     *      <li>
     *          <b>key：</b>流程部署key，对应ACT_RE_DEPLOYMENT表的KEY_字段。
     *      </li>
     *      <li>
     *          <b>tenantId：</b>租户ID，对应ACT_RE_DEPLOYMENT表的TENANT_ID_字段。
     *      </li>
     *      <li>
     *          <b>resources：</b>关联的资源文件。该属性是一个Map，前面介绍的DeploymentBuilder加载流程资源的方法都是将资源文件保存到该Map中。一个Map中可以保存多个资源文件。流程部署通常包
     *          含一个.bpmn文件和一个缩略图文件。
     *      </li>
     *      <li>
     *          <b>isNew：</b>是否全新部署，数据库中无对应字段，true表示全新部署，会生成新的部署记录，false则仅将数据库中的内容加载到缓存中。
     *      </li>
     *      <li>
     *          <b>derivedFrom：</b>派生部署时的来源部署ID，通过DynamicBpmnService动态为流程注入节点或子流程时使用，对应ACT_RE_DEPLOYMENT表的DERIVED_FROM_字段。
     *      </li>
     *      <li>
     *          <b>derivedFromRoot：</b>派生部署时的来源根部署ID，通过DynamicBpmnService动态为流程注入节点或子流程时使用，对应ACT_RE_DEPLOYMENT表的DERIVED_FROM_ROOT_字段。
     *      </li>
     *      <li>
     *          <b>parentDeploymentId：</b>父部署ID，对应ACT_RE_DEPLOYMENT表的PARENT_DEPLOYMENT_ID_字段。
     *      </li>
     *      <li>
     *          <b>engineVersion：</b>工作流引擎版本，主要用于解决工作流引擎向后兼容的问题，对应ACT_RE_ DEPLOYMENT表的ENGINE_VERSION_字段。
     *      </li>
     *      <li>
     *          <b>deploymentTime：</b>部署时间，对应ACT_RE_DEPLOYMENT表的DEPLOY_TIME_字段。
     *      </li>
     *  </ul><br/>
     *
     *  <h3>DeploymentEntityManager：</h3>
     *  <ol>
     *      <li>
     *          <b>getDeploymentResourceNames：</b>根据部署记录ID查询部署资源名称
     *      </li>
     *      <li>
     *          <b>findDeploymentsByNativeQuery：</b>/原生查询，返回分页记录
     *      </li>
     *      <li>
     *          <b>findDeploymentCountByNativeQuery：</b>原生查询，返回记录数量
     *      </li>
     *      <li>
     *          <b>findDeploymentsByQueryCriteria：</b>根据DeploymentQuery查询
     *      </li>
     *      <li>
     *          <b>findDeploymentCountByQueryCriteria：</b>根据DeploymentQuery查询，返回记录数量
     *      </li>
     *      <li>
     *          <b>deleteDeployment：</b>级联删除部署记录
     *      </li>
     *  </ol>
     */

    /**
     * <p><b>用途:部署记录查询API说明<b></p>
     * @author <a href="mailto:blnp.yibin@qq.com">lyb</a>
     * @since 16:35 2025/5/6
     * @params []
     * @param
     * @return void
    **/
    @Test
    public void test1() {
        //根据部署ID查询
        //deploymentId(String deploymentId)

        //根据部署ID批量查询
        //deploymentIds(List<String> deploymentId)

        //根据部署名称查询
        //deploymentName(String name)

        //根据部署名称模糊查询
        //deploymentNameLike(String nameLike)

        //根据部署分类查询
        //deploymentCategory(String category)

        //根据部署分类模糊查询
        //deploymentCategoryLike(String categoryLike)

        //查询排除某个分类后的部署记录
        //deploymentCategoryNotEquals(String categoryNotEquals)

        //根据部署key查询
        //deploymentKey(String key)

        //根据部署key模糊查询
        //deploymentKeyLike(String keyLike)

        //根据租户ID查询
        //deploymentTenantId(String tenantId)

        //根据租户ID模糊查询
        //deploymentTenantIdLike(String tenantIdLike)

        //查询没有租户ID的部署记录
        //deploymentWithoutTenantId()

        //根据工作流引擎版本查询
        //deploymentEngineVersion(String engineVersion)

        //根据派生的部署ID查询
        //deploymentDerivedFrom(String deploymentId)

        //根据父部署ID查询
        //parentDeploymentId(String parentDeploymentId)

        //根据父部署ID模糊查询
        //parentDeploymentIdLike(String parentDeploymentIdLike)

        //根据父部署ID批量查询
        //parentDeploymentIds(List<String> parentDeploymentIds)

        //根据流程定义key查询
        //processDefinitionKey(String key)

        //根据流程定义key模糊查询
        //processDefinitionKeyLike(String keyLike)

        //查找最新的部署记录，一般与部署key条件联合查询
        //latest()
    }

    /**
     * <p><b>用途:根据部署key查找最新的部署记录<b></p>
     * @author <a href="mailto:blnp.yibin@qq.com">lyb</a>
     * @since 16:39 2025/5/6
     * @params [deploymentKey] 
     * @param deploymentKey 
     * @return org.flowable.engine.repository.Deployment
    **/
    public Deployment queryLastDeploymentByKey(String deploymentKey) {
        return repositoryService.createDeploymentQuery()
                //指定部署key
                .deploymentKey(deploymentKey)
                //查找最新版本
                .latest()
                //返回单个记录
                .singleResult();
    }

    /**
     * <p><b>用途:根据部署key查找部署记录列表<b></p>
     * @author <a href="mailto:blnp.yibin@qq.com">lyb</a>
     * @since 16:40 2025/5/6
     * @params [deploymentKey] 
     * @param deploymentKey 
     * @return java.util.List<org.flowable.engine.repository.Deployment>
    **/
    public List<Deployment> queryDeploymentsByKey(String deploymentKey) {
        return repositoryService.createDeploymentQuery()
                //指定流程定义key
                .deploymentKey(deploymentKey)
                //按部署时间排序
                .orderByDeploymentTime()
                //降序
                .desc()
                //返回全部记录
                .list();
    }

    /**
     *  <h2>流程定义查询</h2>
     *  <hr/>
     *
     *  <h3>ProcessDefinitionEntity：</h3>
     *  <ul>
     *      <li>
     *          <b>id：</b>流程定义ID，继承自AbstractEntity类的id属性，对应ACT_RE_PROCDEF表的ID_字段（主键）。
     *      </li>
     *      <li>
     *          <b>name：</b>流程名称，对应ACT_RE_PROCDEF表的NAME_字段。
     *      </li>
     *      <li>
     *          <b>localizedName：</b>本地化流程名称，数据库中无对应字段，只在展示的时候根据配置进行本地化处理
     *      </li>
     *      <li>
     *          <b>description：</b>流程描述，对应ACT_RE_PROCDEF表的DESCRIPTION_字段。
     *      </li>
     *      <li>
     *          <b>localizedDescription：</b>本地化流程描述，数据库中无对应字段，只在展示的时候根据配置进行本地化处理。
     *      </li>
     *      <li>
     *          <b>key：</b>流程的key，对应ACT_RE_PROCDEF表的KEY_字段。
     *      </li>
     *      <li>
     *          <b>version：</b>流程的版本，对应ACT_RE_PROCDEF表的REV_字段，同一个key可以对应多个版本，不同版本之间流程定义的ID是不同的。
     *      </li>
     *      <li>
     *          <b>category：</b>流程分类，对应ACT_RE_PROCDEF表的CATEGORY_字段。
     *      </li>
     *      <li>
     *          <b>deploymentId：</b>部署ID，对应ACT_RE_PROCDEF表的DEPLOYMENT_ID_字段，与ACT_RE_ DEPLOYMENT表中的ID_字段关联。
     *      </li>
     *      <li>
     *          <b>resourceName：</b>资源名称，对应ACT_RE_PROCDEF表的RESOURCE_NAME_字段。
     *      </li>
     *      <li>
     *          <b>tenantId：</b>租户ID，对应ACT_RE_PROCDEF表的TENANT_ID_字段。
     *      </li>
     *      <li>
     *          <b>historyLevel：</b>历史数据级别，在ACT_RE_PROCDEF表中没有对应字段，主要用于判断流程执行过程中是否需要保存历史数据。
     *      </li>
     *      <li>
     *          <b>diagramResourceName：</b>图片资源名称，对应ACT_RE_PROCDEF表的DGRM_RESOURCE_NAME_字段。
     *      </li>
     *      <li>
     *          <b>isGraphicalNotationDefined：</b>是否存在图片信息，对应ACT_RE_PROCDEF表的HAS_GRAPHICAL_ NOTATION_字段。
     *      </li>
     *      <li>
     *          <b>variables：</b>流程定义变量信息，类型为Map，在ACT_RE_PROCDEF表中没有对应字段。
     *      </li>
     *      <li>
     *          <b>hasStartFormKey：</b>是否存在发起表单，对应ACT_RE_PROCDEF表的HAS_START_FORM_KEY_字段。
     *      </li>
     *      <li>
     *          <b>suspensionState：</b>挂起状态，对应ACT_RE_PROCDEF表的SUSPENSION_STATE_字段，已挂起的流程定义不能再发起和流转。
     *      </li>
     *      <li>
     *          <b>ioSpecification：</b>主要用于Webservice场景，数据库中无对应字段。
     *      </li>
     *      <li>
     *          <b>derivedFrom：</b>派生部署的来源流程定义ID，通过DynamicBpmnService动态为流程注入节点或子流程时使用，对应ACT_RE_PROCDEF表的DERIVED_FROM_字段。
     *      </li>
     *      <li>
     *          <b>derivedFromRoot：</b>派生部署的来源根流程定义ID，通过DynamicBpmnService动态为流程注入节点或子流程时使用，对应ACT_RE_PROCDEF表的DERIVED_FROM_ROOT_字段。
     *      </li>
     *      <li>
     *          <b>derivedVersion：</b>派生部署的来源流程定义版本，通过DynamicBpmnService动态为流程注入节点或子流程时使用，对应ACT_RE_PROCDEF表的DERIVED_VERSION_字段。
     *      </li>
     *      <li>
     *          <b>engineVersion：</b>工作流引擎版本，主要用于解决工作流引擎向后兼容的问题，对应ACT_RE_PROCDEF表的ENGINE_VERSION_字段。
     *      </li>
     *  </ul>
     */

    @Test
    public void test2() {
        ProcessDefinitionEntity defEntity = (ProcessDefinitionEntity)queryLastProcessDefinitionByKey("");
        defEntity.setLocalizedName("");
    }

    /**
     * <p><b>用途:根据流程定义key查找最新的流程定义<b></p>
     * @author <a href="mailto:blnp.yibin@qq.com">lyb</a>
     * @since 17:07 2025/5/6
     * @params [processDefinitionKey] 
     * @param processDefinitionKey 
     * @return org.flowable.engine.repository.ProcessDefinition
    **/
    public ProcessDefinition queryLastProcessDefinitionByKey(String processDefinitionKey) {
        return repositoryService.createProcessDefinitionQuery()
                //指定流程定义key
                .processDefinitionKey(processDefinitionKey)
                //指定激活状态
                .active()
                //查找最新版本
                .latestVersion()
                //返回单个记录
                .singleResult();
    }

    /**
     * <p><b>用途:根据租户ID查找流程定义列表<b></p>
     * @author <a href="mailto:blnp.yibin@qq.com">lyb</a>
     * @since 17:08 2025/5/6
     * @params [tenantId] 
     * @param tenantId 
     * @return java.util.List<org.flowable.engine.repository.ProcessDefinition>
    **/
    public List<ProcessDefinition> queryProcessDefinitionByTenantId(String tenantId) {
        return repositoryService.createProcessDefinitionQuery()
                //指定租户ID
                .processDefinitionTenantId(tenantId)
                //按流程定义key排序
                .orderByProcessDefinitionKey()
                //升序
                .asc()
                //返回全部记录
                .list();
    }
    
    @Test
    public void test3() {
        //部署流程
        Deployment deployment = repositoryService.createDeployment()
                //设置部署基本属性
                .key("HolidayRequest")
                .name("请假申请")
                .category("HR")
                .tenantId("HR")
                //添加classpath下的流程定义资源
                .addClasspathResource("processes/HolidayRequest.bpmn20.xml")
                //执行部署
                .deploy();
        log.info("部署记录：deployment_id={}, deployment_name={}",
                deployment.getId(), deployment.getName());
        //查询流程资源
        log.info("部署资源:");
        if (deployment instanceof DeploymentEntity) {
            DeploymentEntity entity = (DeploymentEntity) deployment;
            Map<String, EngineResource> resourceEntityMap = entity.getResources();
            for (Map.Entry<String, EngineResource> resourceEntity :
                    resourceEntityMap.entrySet()) {
                EngineResource entityValue = resourceEntity.getValue();
                log.info("resource_name={}, deployment_id={}", entityValue.getName(), entityValue.getDeploymentId());
            }
        }
        //查询流程定义
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                //指定流程定义key
                .processDefinitionKey("HolidayRequest")
                //指定激活状态
                .active()
                //查找最新版本
                .latestVersion()
                //返回单个记录
                .singleResult();
        log.info("流程定义:processDefinition_id={}, processDefinition_key={}",processDefinition.getId(),processDefinition.getKey());
    }
}
